package club.lovety.spring.demo;

import club.lovety.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author smc
 * @date 2019-01-07 14:09
 * @since
 **/
public class YxtDispatchServlet extends HttpServlet {

    private static final String WEB_CONFIGURATION = "config_applicaiton";

    private static final String PACKAGE_PATH_KEY = "scanner.package";

    private Set<Class> classes = new HashSet<>();

    private Map<String, Object> beanDefinitionMap = new HashMap<>();

    private Map<String, Method> requestMappingMap = new HashMap<>();

    private String BASE_PACKAGE = "";

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("开始加载配置文件");
        String configPath = config.getInitParameter(WEB_CONFIGURATION);
        //加载配置文件
        doLoadConfiguration(configPath);
        //扫描符合条件的class
        doScan(BASE_PACKAGE);
        // 实例化class对象
        doInstance();
        //依赖注入
        doInvoke();
        //处理请求和方法对应关系
        doHandleMapping();
        System.out.println("加载配置完成");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        doDispatch(req, resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        String servletPath = req.getServletPath();

        System.out.printf("servletPath:  %s \n", servletPath);

        Method method = requestMappingMap.get(servletPath.substring(1));

        Object obj = beanDefinitionMap.get(getLowerName(method.getDeclaringClass().getSimpleName()));

        List<Object> params = new ArrayList<>();
        for (Parameter parameter : method.getParameters()) {
            System.out.println("=====");
            YxtRequestParam yxtRequestParam = (YxtRequestParam) parameter.getAnnotation(YxtRequestParam.class);
            if (yxtRequestParam != null) {
                params.add(req.getParameter(yxtRequestParam.value()));
            }
            if (parameter.getType().isInstance(req)) {
                params.add(req);
            }
            if (parameter.getType().isInstance(resp)) {
                params.add(resp);
            }
        }
        try {
            String result = (String) method.invoke(obj, params.toArray());
            resp.getOutputStream().write(result.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(" ===========请求数据完成==========");
    }


    /**
     * 加载配置文件
     */
    private void doLoadConfiguration(String path) {
        InputStream inputStream = YxtDispatchServlet.class.getClassLoader().getResourceAsStream(path);
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
            this.BASE_PACKAGE = properties.getProperty(PACKAGE_PATH_KEY);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScan(String basePackage) {
        try {
            Enumeration<URL> enumeration = Thread.currentThread().getContextClassLoader().getResources(basePackage.replace(".", "/"));
            while (enumeration.hasMoreElements()) {
                URL url = enumeration.nextElement();
                if ("file".equalsIgnoreCase(url.getProtocol())) {
                    scanFileClass(basePackage);
                }
                if ("jar".equalsIgnoreCase(url.getProtocol())) {
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    scanJarClass(jarFile, basePackage);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private Class loadClass(String className) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 扫描本地class符合条件的类
     *
     * @param packageName
     */
    private void scanFileClass(String packageName) {
        URL url = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/"));
        File parent = new File(url.getPath());
        for (File file : parent.listFiles()) {
            if (file.isFile()) {
                classes.add(loadClass(packageName + "." + file.getName().substring(0, file.getName().length() - 6)));
            } else {
                scanFileClass(packageName + "." + file.getName());
            }
        }
    }

    /**
     * 扫描第三方jar中符合条件的
     *
     * @param jarFile
     */
    private void scanJarClass(JarFile jarFile, String basePackage) {
        Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
        while (jarEntryEnumeration.hasMoreElements()) {
            JarEntry jarEntry = jarEntryEnumeration.nextElement();
            if (!jarEntry.isDirectory()) {
                String className = jarEntry.getName();
                if (className.lastIndexOf(".class") > -1 && className.replace("/", ".").indexOf(basePackage) > -1) {
                    className = className.replace("/", ".");
                    className = className.substring(0, className.length() - 6);
                    classes.add(loadClass(className));
                }
            }
        }
    }

    private void doInstance() {
        for (Class cls : classes) {
            if (!cls.isInterface()) {
                YxtComponent yxtComponent = (YxtComponent) cls.getAnnotation(YxtComponent.class);
                YxtController yxtController = (YxtController) cls.getAnnotation(YxtController.class);
                if (yxtComponent != null || yxtController != null) {
                    String name = getLowerName(cls.getSimpleName());
                    try {
                        beanDefinitionMap.put(name, cls.newInstance());
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    if (yxtController != null) {
                        String prefixUrl = "";
                        YxtRequestMapping yxtRequestMapping = (YxtRequestMapping) cls.getAnnotation(YxtRequestMapping.class);
                        if (yxtRequestMapping != null) {
                            prefixUrl = yxtRequestMapping.value();
                        }
                        Method[] methods = cls.getDeclaredMethods();
                        for (Method method : methods) {
                            yxtRequestMapping = (YxtRequestMapping) method.getAnnotation(YxtRequestMapping.class);
                            if (yxtRequestMapping != null) {
                                String methodPath = ("/" + yxtRequestMapping.value()).replaceAll("/+", "/");
                                requestMappingMap.put(prefixUrl + methodPath, method);
                            }
                        }
                    }
                }
            }
        }
    }


    private void doInvoke() {
        for (Class cls : classes) {
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(YxtAutowired.class)) {
                    String fieldName = field.getType().getSimpleName();
                    System.out.printf("fieldName:  %s \n", fieldName);
                    field.setAccessible(true);
                    try {
                        field.set(beanDefinitionMap.get(getLowerName(cls.getSimpleName())), beanDefinitionMap.get(getLowerName(fieldName)));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void doHandleMapping() {

    }

    private String getLowerName(String name) {
        char[] nameCharArray = name.toCharArray();
        nameCharArray[0] = (char) (nameCharArray[0] + 32);
        return new String(nameCharArray);
    }


}
